
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libpatches.h"
#include "modules.h"

static initializer find_initializer(char * name);
static void process_modules(void);


static module modules;
static hash_table module_types;
static hash_table module_names;

static sample sample_zero = 0;
static sample sample_one = 1;
static output zero_output = &sample_zero;
static output one_output = &sample_one;

void install_module(char * name, initializer i) { hash_set(module_types, name, i); }

module new_module(char * name) {
	static module tail = 0;
	fprintf(stderr, "creating new %s\n", name);

	module m;
	m = (module) new_garbage(1, sizeof(struct module));

	if (! modules) modules = m;
	if (tail) tail->next = m;

	m->next = 0;
	tail = m;

	m->type = name;

	m->processor = null_processor;
	m->customizer = null_customizer;

	initializer f = find_initializer(name);
	f(m);

	return m;
}

void init(module m, char * name, char * value) {
	m->customizer(m, name, value);
}

input * get_input(module m, char * name) {
	int count = count_named_inputs(m);
	input * i = get_named_inputs(m);
	char ** n = get_input_names(m);

	for(;count--; n++, i++)
		if ( *i && *n && strcmp(name, *n) == 0) return i;

	count = atoi(name);
	fprintf(stderr, "looking for input %d of %d\n", count, count_inputs(m));
	if (count > 0 && count <= count_inputs(m))
		return get_inputs(m) + --count;

	return 0;
}

input * get_inputs(module m) { return m->inputs; }
input * get_named_inputs(module m) { return m->named_inputs; }
char ** get_input_names(module m) { return m->input_names; }

int count_inputs(module m) { return m->input_count; }
int count_named_inputs(module m) { return m->named_input_count; }

output * get_output(module m, char * name) {
	int count = count_named_outputs(m);
	output * o = get_named_outputs(m);
	char ** n = get_output_names(m);

	for(;count--; n++, o++)
		if ( *o && *n && strcmp(name, *n) == 0) return o;

	count = atoi(name);
	fprintf(stderr, "looking for output %d of %d\n", count, count_inputs(m));
	if (count > 0 && count <= count_outputs(m))
		return get_outputs(m) + --count;

	return 0;
}

output * get_outputs(module m) { return m->outputs; }
output * get_named_outputs(module m) { return m->named_outputs; }
char ** get_output_names(module m) { return m->output_names; }

int count_outputs(module m) { return m->output_count; }
int count_named_outputs(module m) { return m->named_output_count; }

void patch(input * i, output * o) { 
	fprintf(stderr, "patch %p <- %p; %f <- %f\n", i, o, ***i, **o);
	*i = o; 
	fprintf(stderr, "%p = %f\n", i, ***i);
}

void patches(input * i, output * o, int count) {
	for (;count--; i++, o++) patch(i, o);
}

void multiple(input * i, output * o, int count) {
	for (;count--; i++) patch(i, o);
}

sample strtosample (char * s) { return atof(s); }

output * constant(sample v) {
	output c = (output) new_garbage(1, sizeof(sample));
	output * o = (output *) new_garbage(1, sizeof(output));
	*c = v;
	*o = c;
	fprintf(stderr, "constant %f->%f\n", v, *c);
	return o;
}

void patch_one(input * i) {
	patch(i, &one_output);
}

void patch_zero(input * i) {
	patch(i, &zero_output);
}

void patch_constant(input * i, sample v) {
	patch(i, constant(v));
}

void patches_constant(input * i, sample * v, int count) {
	for (;count--; i++, v++) patch_constant(i, *v);
}

void multiple_constant(input * i, sample v, int count) {
	multiple(i, constant(v), count);
}

/* private functions */

void null_initializer(module m) {
	fprintf(stderr, "null module '%s'\n", m->type);
}

void null_processor(module m) { }

void startup(void) {
	extern counter samples;
	extern double seconds;
	extern int sampling_rate;

	sampling_rate = 44100;
	samples = 0;
	seconds = 0;

	modules = 0;
	module_types = 0;
	module_names = 0;

	free_garbage();

	module_types = new_hash_table(97, 0);
	module_names = new_hash_table(97, 0);
	install_modules();
}

void process(counter cycles) {
	if (cycles)
		for(;cycles;--cycles)
			process_modules();
	else
		for(;;)
			process_modules();
}

void null_customizer(module m, char * name, char * value) { }

void customizer_count_inputs(module m, char * name, char * value) {
	int count;

	if (strcmp(name, "count") != 0) { return; }

	count = atoi(value);

	if (count < 1) return;

	make_inputs(m, count);
}

void customizer_count_inputs_only(module m, char * name, char * value) {
	int count;

	if (strcmp(name, "count") != 0) { return; }

	count = atoi(value);

	if (count < 1) return;
	m->input_count = count;
}

void customizer_output_buffer_size(module m, char * name, char * value) {
	int size;

	if (strcmp(name, "buffer") != 0) { return; }

	size = atoi(value);

	if (size < 1) return;

	setvbuf(stdout, 0, _IOFBF, size);
}

void customizer_buffer_seconds(module m, char * name, char * value) {
	int size;

	if (strcmp(name, "seconds") != 0) { return; }

	size = abs(sampling_rate * atof(value));

	if (size < 1) size = 1;

	make_buffer(m, size);
}

void customizer_buffer_size(module m, char * name, char * value) {
	int size;

	if (strcmp(name, "buffer") != 0) { return; }

	size = atoi(value);

	if (size < 1) size = 1;

	make_buffer(m, size);
}

static initializer find_initializer(char * name) {
	initializer f = (initializer) hash_get(module_types, name);
	return f ? f : null_initializer;
}

static void process_modules(void) {
	module m;
	for(m = modules; m; m = m->next) m->processor(m);
}


void make_named_inputs(module m, int count, int named_count) {
	input * i;
	char ** n;

	i = (input *) new_garbage(count + named_count, sizeof(input));
	n = (char **) new_garbage(named_count, sizeof(char *));

	m->inputs = i;
	m->named_inputs = i + count;
	m->input_names = n;
	m->input_count = count;
	m->named_input_count = named_count;


	fprintf(stderr, "making %d inputs + %d named\n", count, named_count);
	fprintf(stderr, "inputs: %p, named: %p\n", m->inputs, m->named_inputs);

	for (;count--; i++)
		*i = &zero_output;
	for (;named_count--; i++)
		*i = &zero_output;
}


void make_named_outputs(module m, int count, int named_count) {
	sample * s;
	output * o;
	char ** n;

	s = (sample *) new_garbage(count + named_count, sizeof(sample));
	o = (output *) new_garbage(count + named_count, sizeof(output));
	n = (char **) new_garbage(named_count, sizeof(char *));

	m->outputs = o;
	m->named_outputs = o + count;
	m->output_names = n;
	m->output_count = count;
	m->named_output_count = named_count;


	fprintf(stderr, "making %d outputs + %d named\n", count, named_count);
	fprintf(stderr, "outputs: %p, named: %p\n", m->outputs, m->named_outputs);

	while (count--) *o++ = s++;
	while (named_count--) *o++ = s++;

}

void make_inputs(module m, int count) {
	input * i;
	char ** n;

	i = (input *) new_garbage(count, sizeof(input));
	n = (char **) new_garbage(count, sizeof(char *));

	m->inputs = i;
	m->named_inputs = i;
	m->input_names = n;
	m->input_count = count;
	m->named_input_count = count;

	fprintf(stderr, "making %d inputs\n", count);
	for (;count--; i++)
		*i = &zero_output;
}

void make_outputs(module m, int count) {
	sample * s = (sample *) new_garbage(count, sizeof(sample));
	output * o = (output *) new_garbage(count, sizeof(output));
	char ** n = (char **) new_garbage(count, sizeof(char *));

	m->outputs = o;
	m->named_outputs = o;
	m->output_names = n;
	m->output_count = count;
	m->named_output_count = count;

	fprintf(stderr, "making %d outputs\n", count);
	while (count--) *o++ = s++;
}

void make_buffer(module m, int size) {
	m->buffer = (sample *) new_garbage(size, sizeof(sample));
	m->buffer_garbage = (garbage) garbage_pile();
	m->buffer_size = size;
	fprintf(stderr, "created buffer %p size %d\n", m->buffer, m->buffer_size);
}

void resize_buffer(module m, int size) {
	//sample * buffer;
	//sample * t;
	fprintf(stderr, "resize buffer from %d to %d\n", m->buffer_size, size);
	if (size <= m->buffer_size) return;
	m->buffer = (sample *) realloc(m->buffer, (size * sizeof(sample)));
	m->buffer_garbage->value = (void *) m->buffer; 
	memset(m->buffer + m->buffer_size, 0, (size - m->buffer_size) * sizeof(sample));
	m->buffer_size = size;
}

void safe_module(char * type, char * name) {
	fprintf(stderr, "============safe_module %s %s\n", type, name);
	hash_set(module_names, name, new_module(type));
}

void safe_init(char * mod, char * key, char * val) {
	module m;
	m = (module) hash_get(module_names, mod);
	fprintf(stderr, "trying to init %s %s %s %p\n", mod, key, val, m);
	if (m)
		init(m, key, val);
}

void safe_patch(char * in_mod, char * in_stream, char * out_mod, char * out_stream) {
	input * i;
	output * o;
	module in;
	module out;

	fprintf(stderr, "safe_patch %s:%s <- %s:%s\n", in_mod, in_stream, out_mod, out_stream);

	if(!(in = hash_get(module_names, in_mod))) return;
	fprintf(stderr, "found input module '%s' %p\n", in_mod, in);
	
	if(!(out = hash_get(module_names, out_mod))) return;
	fprintf(stderr, "found output module '%s' %p\n", out_mod, out);

	if(!(i = get_input(in, in_stream))) return;
	fprintf(stderr, "found input '%s' %p\n", in_stream, i);

	if(!(o = get_output(out, out_stream))) return;
	fprintf(stderr, "found output '%s' %p\n", out_stream, o);

	patch(i, o);

}

void safe_patch_constant(char * in_mod, char * in_stream, char * val) {
	input * i;
	module in;
	sample v;

	v = strtosample(val);

	if(!(in = hash_get(module_names, in_mod))) return;
	fprintf(stderr, "found input module '%s' %p\n", in_mod, in);

	if(!(i = get_input(in, in_stream))) return;
	fprintf(stderr, "found input '%s' %p\n", in_stream, i);

	patch_constant(i, v);
	
}

void safe_patch_all(char * in_mod, char * out_mod) {
}

void safe_set_seconds(char * s) {
	extern double seconds;
	extern int sampling_rate;
	extern counter samples;

	seconds = atof(s);
	if (seconds < 0) seconds = -seconds;
	samples = seconds * sampling_rate;
	if (! samples) samples = 1;
}

void safe_set_samples(char * s) {
	extern double seconds;
	extern int sampling_rate;
	extern counter samples;

	samples = atoi(s);
	if (samples < 0) samples = -samples;

	seconds = samples/sampling_rate;
}

void safe_set_sampling_rate(char * s) {
	extern double seconds;
	extern int sampling_rate;
	extern counter samples;

	int sr;

	sr = atoi(s);

	if (! sr) return;
	if (sr < 0) sr = - sr;

	sampling_rate = sr;
	if (samples) {
		samples = seconds * sampling_rate;
		if (! samples) samples = 1;
	}

}

